/**
 * The Free Gaming System; socket communication management.
 *
 * Copyright 2006 the Free Gaming System developers.
 *
 * FGS is the legal property of its developers, whose names are too
 * numerous to list here.  Please refer to the COPYRIGHT file
 * distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <assert.h>

#include "fgs_game.h"
#include "fgs_internal.h"


static fgs_error_t
send_line(fgs_baton_t sock, conn_write_cb_t *conn_write, char *line)
{
  fgs_error_t err;
  assert(line != NULL);

  err = conn_write(sock, line, strlen(line));
  if (err != FGS_OK)
    return err;

  err = conn_write(sock, "\n", 1);
  if (err != FGS_OK)
    return err;

  return FGS_OK;
}


static fgs_error_t
recv_line(fgs_baton_t sock, conn_read_cb_t *conn_read, char **line, int *len)
{
  char buf[256];
  int buf_pos = 0;
  fgs_error_t err;

  while (1)
    {
      err = conn_read(sock, &buf[buf_pos], 1, FGS_TRUE);
      if (err != FGS_OK)
        return err;

      if (buf[buf_pos] == '\n')
        {
          buf[buf_pos] = 0;
          *line = fgs__alloc(buf_pos+1, sizeof(char));
          strncpy(*line, buf, buf_pos+1);
          *len = buf_pos;
          return FGS_OK;
        }

      buf_pos++;
      /* Line overflow, the peer is borky. Just return an error. */
      if (buf_pos == 256)
        return FGS_ERR_REPLY_LINE_TOO_LONG;
    }
}


static fgs_error_t
send_end_command(fgs_baton_t sock, conn_write_cb_t *conn_write)
{
  return conn_write(sock, "\0\n", 2);
}


fgs_error_t
fgs__socket_send(fgs_baton_t socket, conn_write_cb_t *conn_write,
                 char *command, const char *fmt, ...)
{
  const char *fmt_ptr = fmt;
  va_list ap;
  assert(command != NULL && fmt != NULL);

  FGS_ERR(send_line(socket, conn_write, command));

  /* Start processing the variable args */
  va_start(ap, fmt);

  while(*fmt_ptr)
    {
      char *s;
      signed long int i;
      double d;
      fgs_bool_t b;
      char buf[16];

      switch (*fmt_ptr++)
        {
        case 's': /* string */
          s = va_arg(ap, char *);
          FGS_ERR(send_line(socket, conn_write, s));
          break;
        case 'i':
          i = va_arg(ap, signed long int);
          snprintf(buf, 16, "%ld", i);
          FGS_ERR(send_line(socket, conn_write, buf));
          break;
        case 'd':
          d = va_arg(ap, double);
          snprintf(buf, 16, "%e", d);
          FGS_ERR(send_line(socket, conn_write, buf));
          break;
        case 'b':
          b = va_arg(ap, fgs_bool_t);
          if (b == FGS_TRUE)
            FGS_ERR(send_line(socket, conn_write, "true"));
          else
            FGS_ERR(send_line(socket, conn_write, "false"));
          break;
        case 'n':
          FGS_ERR(send_line(socket, conn_write, ""));
        }
    }

  FGS_ERR(send_end_command(socket, conn_write));
  va_end(ap);
  return FGS_OK;
}

fgs_error_t
fgs__socket_recv(fgs_baton_t socket, conn_read_cb_t *conn_read,
                 fgs__socket_reply_t **reply)
{
  char *line;
  int line_len;
  fgs__socket_reply_t *rep;
  assert(reply != NULL);

  rep = fgs__alloc(1, sizeof(*rep));

  /* Read and process the return code */
  FGS_ERR(recv_line(socket, conn_read, &line, &line_len));

  rep->result = line;

  /* Read all other results and push them in the reply, up to the
     terminator. */
  while (1)
    {
      FGS_ERR(recv_line(socket, conn_read, &line, &line_len));
      if (line_len == 1 && *line == '\0')
        {
          fgs__free(line);
          *reply = rep;
          return FGS_OK;
        }

      rep->argc++;
      rep->argv = fgs__realloc(rep->argv, rep->argc, sizeof(*rep->argv));
      rep->argv[rep->argc-1] = line;
    }
}


fgs_error_t
fgs__socket_free_reply(fgs__socket_reply_t *reply, fgs_bool_t clear_vals)
{
  assert(reply != NULL);

  fgs__free(reply->result);
  if (clear_vals == FGS_TRUE)
    {
      int i;
      for (i = 0; i < reply->argc; i++)
        if (reply->argv[i] != NULL)
          fgs__free(reply->argv[i]);
    }
  fgs__free(reply->argv);
  fgs__free(reply);

  return FGS_OK;
}


fgs_error_t
fgs__socket_recv_len(fgs_baton_t socket, conn_read_cb_t *conn_read,
                     char **data, int len)
{
  assert(conn_read != NULL && data != NULL && len > 0);

  *data = fgs__alloc(len, sizeof(**data));
  if (*data == NULL)
    return FGS_ERR_INTERNAL;

  return conn_read(socket, *data, len, FGS_TRUE);
}
